// -----------------------------------------------------------
// Game Framework
// Copyright © 2024-2024 deadcycle. All rights reserved.
// Homepage: https://deadcycle.gitee.io
// Feedback: deadcycle@163.com
// -----------------------------------------------------------

using System;

using UnityEngine;
using UnityEngine.Sprites;
using UnityEngine.UI;

namespace UIWidget
{
    /// <summary>
    /// 图片Filled支持九宫模式的网格效果。
    /// </summary>
    [RequireComponent(typeof(Image)), DisallowMultipleComponent]
    public class SlicedFilledMeshEffect : BaseMeshEffect
    {
        private const int k_InvalidIndex = -1;

        #region 静态字段
        private static readonly Vector2[] s_VertScratch = new Vector2[4];
        private static readonly Vector2[] s_UVScratch = new Vector2[4];

        // 最多需要4x4个顶点
        private static readonly Vector3[] s_DrawVerts = new Vector3[4 * 4];
        private static readonly Vector2[] s_DrawUVs = new Vector2[4 * 4];
        private static readonly int[] s_DrawIndices = new int[4 * 4];

        private static bool s_IsInvalidLeftBottom;
        private static bool s_IsInvalidLeftTop;
        private static bool s_IsInvalidRightBottom;
        private static bool s_IsInvalidRightTop;
        #endregion

        #region 字段
        private Image m_ImageGraphic;

        // 对性能运行提升不大, 仅减少运行时对cpu无意义的消耗.(sprite运行使用时一般不会变化了)
        private Vector2 m_CacheSizeDelta;
        private RectTransform m_CacheRectTransform;
        private Sprite m_CacheSprite;
        private Vector4 m_CacheSpriteBorder;

        private Vector4 m_SpriteOuter;
        private Vector4 m_SpriteInner;
        private Vector4 m_SpritePadding;

        private bool m_IsBorderLeftZero;
        private bool m_IsBorderRightZero;
        private bool m_IsBorderBottomZero;
        private bool m_IsBorderTopZero;

        [SerializeField]
        private bool m_FillCenter = true;
        #endregion

        #region 属性
        /// <summary> Image图形。 </summary>
        private Image ImageGraphic
        {
            get
            {
                if (m_ImageGraphic == null)
                {
                    if (graphic is not Image)
                    {
                        Debug.LogError(
                            "SlicedFilledMeshEffect target graphic only support image component. (仅支持Image组件)");

                        return null;
                    }

                    m_ImageGraphic = graphic as Image;
                }

                return m_ImageGraphic;
            }
        }

        private RectTransform CacheRectTransform =>
            m_CacheRectTransform ??= GetComponent<RectTransform>();
        #endregion

        public override void ModifyMesh(VertexHelper vh)
        {
            if (!IsActive())
            {
                return;
            }

            if (ImageGraphic == null)
            {
                return;
            }

            Image.Type imageType = ImageGraphic.type;

            if (imageType != Image.Type.Filled)
            {
                Debug.LogError(
                    $"SlicedFilledMeshEffect only support image filled type. (仅支持Filled模式)");

                return;
            }

            if (ImageGraphic.overrideSprite == null && ImageGraphic.sprite == null)
            {
                Debug.LogWarning($"Image component without sprite. (未设置sprite)");

                m_CacheSprite = null;

                return;
            }

            if (!ImageGraphic.hasBorder)
            {
                Debug.LogError("Sprite need set border. (Sprite需要设置border)");

                return;
            }

            Image.FillMethod fillMethod = ImageGraphic.fillMethod;

            if (fillMethod is not Image.FillMethod.Horizontal and not Image.FillMethod.Vertical)
            {
                Debug.LogError(
                    "SlicedFilledMeshEffect only support horizontal or vertical fillMethod.(仅支持水平或垂直模式)");
            }

            GenerateSlicedFilledSprite(vh);
        }

        /// <summary>
        /// 生成Filled模式的所有9宫区域精灵(但不一定需要9个9宫格子)。
        /// </summary>
        private void GenerateSlicedFilledSprite(VertexHelper vh)
        {
            vh.Clear();

            if (ImageGraphic.fillAmount < 0.001f)
            {
                return;
            }

            // 计算各个区域Rect, sprite, sprite的border, Image的显示区域变化, 需要重新计算
            bool isDirty = false;

            Sprite sprite = ImageGraphic.overrideSprite != null
                ? ImageGraphic.overrideSprite
                : ImageGraphic.sprite;
            Vector4 spriteBorder = sprite.border;

            if (m_CacheSprite != sprite || m_CacheSpriteBorder != spriteBorder)
            {
                isDirty = true;

                m_CacheSprite = sprite;
                m_CacheSpriteBorder = spriteBorder;
                OnSpriteChanged();
            }

            if (m_CacheSizeDelta != CacheRectTransform.sizeDelta)
            {
                isDirty = true;

                m_CacheSizeDelta = CacheRectTransform.sizeDelta;
            }

            if (isDirty)
            {
                CalcSlicedRect();
            }

            float rectStartPx; // 区域起始像素位置
            float unitPxPercent; //1像素在显示区域的占比
            Image.FillMethod fillMethod = ImageGraphic.fillMethod;
            int fillOrigin = ImageGraphic.fillOrigin;

            if (fillMethod == Image.FillMethod.Horizontal)
            {
                // 水平模式
                rectStartPx = fillOrigin == 0 ? s_VertScratch[0].x : s_VertScratch[3].x;

                float totalSize = s_VertScratch[3].x - s_VertScratch[0].x;
                unitPxPercent = totalSize > 0f ? 1f / totalSize : 1f;
            }
            else
            {
                // 垂直模式
                rectStartPx = fillOrigin == 0 ? s_VertScratch[0].y : s_VertScratch[3].y;

                float totalSize = s_VertScratch[3].y - s_VertScratch[0].y;
                unitPxPercent = totalSize > 0f ? 1f / totalSize : 1f;
            }

            // 重置数组
            Array.Fill(s_DrawIndices, k_InvalidIndex);

            float fillAmount = ImageGraphic.fillAmount;

            for (int leftX = 0; leftX < 3; leftX++)
            {
                int rightX = leftX + 1;

                // border left或right为0, 对应的x列永远不会渲染, 减少三角面与顶点数
                if ((m_IsBorderLeftZero && leftX == 0) || (m_IsBorderRightZero && rightX == 3))
                {
                    continue;
                }

                for (int bottomY = 0; bottomY < 3; bottomY++)
                {
                    if (!m_FillCenter && leftX == 1 && bottomY == 1)
                    {
                        continue;
                    }

                    int topY = bottomY + 1;

                    // border bottom或top为0, 对应的y列永远不会渲染, 减少三角面与顶点数
                    if ((m_IsBorderBottomZero && bottomY == 0) || (m_IsBorderTopZero && topY == 3))
                    {
                        continue;
                    }

                    // 计算当前9宫格子区域的起始位置所占比例
                    float sliceStartPercent, sliceEndPercent;

                    if (fillMethod == Image.FillMethod.Horizontal)
                    {
                        if (fillOrigin == 0)
                        {
                            // 水平从左往右
                            sliceStartPercent =
                                (s_VertScratch[leftX].x - rectStartPx) * unitPxPercent;

                            sliceEndPercent =
                                (s_VertScratch[rightX].x - rectStartPx) * unitPxPercent;
                        }
                        else
                        {
                            // 水平从右往左
                            sliceStartPercent =
                                (rectStartPx - s_VertScratch[rightX].x) * unitPxPercent;

                            sliceEndPercent =
                                (rectStartPx - s_VertScratch[leftX].x) * unitPxPercent;
                        }
                    }
                    else
                    {
                        if (fillOrigin == 0)
                        {
                            // 垂直从下往上
                            sliceStartPercent = (s_VertScratch[bottomY].y - rectStartPx) *
                                unitPxPercent;
                            sliceEndPercent = (s_VertScratch[topY].y - rectStartPx) * unitPxPercent;
                        }
                        else
                        {
                            // 垂直从上往下
                            sliceStartPercent =
                                (rectStartPx - s_VertScratch[topY].y) * unitPxPercent;

                            sliceEndPercent = (rectStartPx - s_VertScratch[bottomY].y) *
                                unitPxPercent;
                        }
                    }

                    // fillAmount 未在当前9宫格子起始区域, 跳过渲染
                    if (sliceStartPercent >= fillAmount)
                    {
                        continue;
                    }

                    // 计算构成当前9宫格子完整rect的顶点与uv
                    Vector4 vertices = new Vector4(s_VertScratch[leftX].x, s_VertScratch[bottomY].y,
                        s_VertScratch[rightX].x, s_VertScratch[topY].y);

                    Vector4 uvs = new Vector4(s_UVScratch[leftX].x, s_UVScratch[bottomY].y,
                        s_UVScratch[rightX].x, s_UVScratch[topY].y);

                    // 计算当前9宫格子的填充比例
                    float sliceFillAmount = (fillAmount - sliceStartPercent) /
                        (sliceEndPercent - sliceStartPercent);

                    // Debug.Log($"第{bottomY + leftX * 3}块被渲染");
                    GenerateFilledSprite(vh, vertices, uvs, sliceFillAmount, leftX, bottomY);
                }
            }
        }

        /// <summary>
        /// 生成单个9宫格的精灵。
        /// </summary>
        /// <param name="vh">顶点辅助器。</param>
        /// <param name="vertices">该9宫格完整顶点数据。</param>
        /// <param name="uvs">该9宫格完整uv数据。</param>
        /// <param name="fillAmount">该9宫格的填充比例。</param>
        /// <param name="x">该9宫格左下顶点x索引。</param>
        /// <param name="y">该9宫格左下顶点y索引。</param>
        private void GenerateFilledSprite(VertexHelper vh, Vector4 vertices, Vector4 uvs,
            float fillAmount, int x, int y)
        {
            /* 计算此9宫格, 4个顶点对应的索引值, 索引值图如下
             *
             *      3----7----11---15
             *      |    |    |    |
             *      2----6----10---14
             *      |    |    |    |
             *      1----5----9----13
             *      |    |    |    |
             *      0----4----8----12
             *
             */

            int leftBottomIndex = x * 4 + y;
            int leftTopIndex = leftBottomIndex + 1;
            int rightBottomIndex = leftBottomIndex + 4;
            int rightTopIndex = leftTopIndex + 4;

            s_IsInvalidLeftBottom = s_DrawIndices[leftBottomIndex] == k_InvalidIndex;
            s_IsInvalidLeftTop = s_DrawIndices[leftTopIndex] == k_InvalidIndex;
            s_IsInvalidRightBottom = s_DrawIndices[rightBottomIndex] == k_InvalidIndex;
            s_IsInvalidRightTop = s_DrawIndices[rightTopIndex] == k_InvalidIndex;

            if (s_IsInvalidLeftBottom ||
                s_IsInvalidLeftTop ||
                s_IsInvalidRightBottom ||
                s_IsInvalidRightTop)
            {
                // 当有顶点索引无效, 计算出4个顶点数据
                Image.FillMethod fillMethod = ImageGraphic.fillMethod;
                int fillOrigin = ImageGraphic.fillOrigin;

                Color color = ImageGraphic.color;

                // 进度不足1时, 根据进度计算新的顶点
                if (fillAmount < 1f)
                {
                    if (fillMethod == Image.FillMethod.Horizontal)
                    {
                        if (fillOrigin == 0)
                        {
                            // 水平从左往右
                            vertices.z = vertices.x + (vertices.z - vertices.x) * fillAmount;
                            uvs.z = uvs.x + (uvs.z - uvs.x) * fillAmount;
                        }
                        else
                        {
                            // 水平从右往左
                            vertices.x = vertices.z - (vertices.z - vertices.x) * fillAmount;
                            uvs.x = uvs.z - (uvs.z - uvs.x) * fillAmount;
                        }
                    }
                    else
                    {
                        if (fillOrigin == 0)
                        {
                            // 垂直从下往上
                            vertices.w = vertices.y + (vertices.w - vertices.y) * fillAmount;
                            uvs.w = uvs.y + (uvs.w - uvs.y) * fillAmount;
                        }
                        else
                        {
                            // 垂直从上往下
                            vertices.y = vertices.w - (vertices.w - vertices.y) * fillAmount;
                            uvs.y = uvs.w - (uvs.w - uvs.y) * fillAmount;
                        }
                    }
                }

                int index;

                if (s_IsInvalidLeftBottom)
                {
                    index = vh.currentVertCount;

                    s_DrawVerts[index].Set(vertices.x, vertices.y, 0);
                    s_DrawUVs[index].Set(uvs.x, uvs.y);
                    vh.AddVert(s_DrawVerts[index], color, s_DrawUVs[index]);

                    s_DrawIndices[leftBottomIndex] = index;
                }

                if (s_IsInvalidLeftTop)
                {
                    index = vh.currentVertCount;

                    s_DrawVerts[index].Set(vertices.x, vertices.w, 0);
                    s_DrawUVs[index].Set(uvs.x, uvs.w);
                    vh.AddVert(s_DrawVerts[index], color, s_DrawUVs[index]);

                    s_DrawIndices[leftTopIndex] = index;
                }

                if (s_IsInvalidRightTop)
                {
                    index = vh.currentVertCount;

                    s_DrawVerts[index].Set(vertices.z, vertices.w, 0);
                    s_DrawUVs[index].Set(uvs.z, uvs.w);
                    vh.AddVert(s_DrawVerts[index], color, s_DrawUVs[index]);

                    s_DrawIndices[rightTopIndex] = index;
                }

                if (s_IsInvalidRightBottom)
                {
                    index = vh.currentVertCount;

                    s_DrawVerts[index].Set(vertices.z, vertices.y, 0);
                    s_DrawUVs[index].Set(uvs.z, uvs.y);
                    vh.AddVert(s_DrawVerts[index], color, s_DrawUVs[index]);

                    s_DrawIndices[rightBottomIndex] = index;
                }
            }

            vh.AddTriangle(s_DrawIndices[leftBottomIndex], s_DrawIndices[leftTopIndex],
                s_DrawIndices[rightTopIndex]);

            vh.AddTriangle(s_DrawIndices[rightTopIndex], s_DrawIndices[rightBottomIndex],
                s_DrawIndices[leftBottomIndex]);
        }

        /// <summary>
        /// image的sprite变化。
        /// </summary>
        private void OnSpriteChanged()
        {
            Sprite activeSprite = m_CacheSprite;

            // 获取sprite在纹理图中的uv坐标
            m_SpriteOuter = DataUtility.GetOuterUV(activeSprite);
            // 获取sprite纹理内部可拉伸区域的uv坐标
            m_SpriteInner = DataUtility.GetInnerUV(activeSprite);

            // 实际渲染区域, 左下角与右上角的uv坐标 
            s_UVScratch[0].Set(m_SpriteOuter.x, m_SpriteOuter.y);
            s_UVScratch[3].Set(m_SpriteOuter.z, m_SpriteOuter.w);

            // 计算出9宫区域最中间区域, 左下角与右上角的uv坐标 
            s_UVScratch[1].Set(m_SpriteInner.x, m_SpriteInner.y);
            s_UVScratch[2].Set(m_SpriteInner.z, m_SpriteInner.w);

            // 获取sprite在atlas纹理图中的padding
            float multipliedPixelsPerUnit =
                ImageGraphic.pixelsPerUnit * ImageGraphic.pixelsPerUnitMultiplier;
            m_SpritePadding = DataUtility.GetPadding(activeSprite);
            m_SpritePadding /= multipliedPixelsPerUnit;

            Vector4 border = m_CacheSpriteBorder;
            m_IsBorderLeftZero = border.x == 0;
            m_IsBorderRightZero = border.z == 0;
            m_IsBorderBottomZero = border.y == 0;
            m_IsBorderTopZero = border.w == 0;
        }

        private void CalcSlicedRect()
        {
            // 获取sprite在atlas纹理图中的padding
            float multipliedPixelsPerUnit =
                ImageGraphic.pixelsPerUnit * ImageGraphic.pixelsPerUnitMultiplier;

            Rect rect = ImageGraphic.GetPixelAdjustedRect();

            Vector4 adjustedBorders =
                GetAdjustedBorders(m_CacheSpriteBorder / multipliedPixelsPerUnit, rect);

            // 分别计算除去padding后, 实际渲染区域, 左下角与右上角的顶点坐标
            s_VertScratch[0].Set(m_SpritePadding.x, m_SpritePadding.y);
            s_VertScratch[3].Set(rect.width - m_SpritePadding.z, rect.height - m_SpritePadding.w);

            // 计算9宫区域最中间区域, 左下角与右下角的顶点坐标
            s_VertScratch[1].x = adjustedBorders.x;
            s_VertScratch[1].y = adjustedBorders.y;

            s_VertScratch[2].x = rect.width - adjustedBorders.z;
            s_VertScratch[2].y = rect.height - adjustedBorders.w;

            // 转换局部坐标到其RectTransform的坐标系
            for (int i = 0; i < 4; i++)
            {
                s_VertScratch[i].x += rect.x;
                s_VertScratch[i].y += rect.y;
            }
        }

        private Vector4 GetAdjustedBorders(Vector4 border, Rect adjustedRect)
        {
            Rect originalRect = CacheRectTransform.rect;

            for (int axis = 0; axis <= 1; axis++)
            {
                float borderScaleRatio;

                // The adjusted rect (adjusted for pixel correctness)
                // may be slightly larger than the original rect.
                // Adjust the border to match the adjustedRect to avoid
                // small gaps between borders (case 833201).
                if (originalRect.size[axis] != 0)
                {
                    borderScaleRatio = adjustedRect.size[axis] / originalRect.size[axis];
                    border[axis] *= borderScaleRatio;
                    border[axis + 2] *= borderScaleRatio;
                }

                // If the rect is smaller than the combined borders, then there's not room for the borders at their normal size.
                // In order to avoid artefacts with overlapping borders, we scale the borders down to fit.
                float combinedBorders = border[axis] + border[axis + 2];

                if (adjustedRect.size[axis] < combinedBorders && combinedBorders != 0)
                {
                    borderScaleRatio = adjustedRect.size[axis] / combinedBorders;
                    border[axis] *= borderScaleRatio;
                    border[axis + 2] *= borderScaleRatio;
                }
            }

            return border;
        }
    }
}